﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
//using MvcPatch;
using System.Collections.ObjectModel;
using System.Web.Mvc;
using System.Reflection;

namespace MvcShop.Extensions
{
    public class ActionRouteDescriptor
    {
        private ReadOnlyCollection<Func<IRouteBinder>> m_binderFactories;

        public MethodInfo ActionInfo { get; private set; }

        public ActionRouteDescriptor(MethodInfo actionInfo)
        {
            if (actionInfo == null) throw new ArgumentNullException("actionInfo");

            this.ActionInfo = actionInfo;
            this.Name = GetName(actionInfo);
            //this.Area = AreaHelper.GetArea(actionInfo.ReflectedType);
            //this.Area = GetArea(actionInfo.ReflectedType);
            this.RouteName = GetRouteName(actionInfo);
            this.ControllerName = GetControllerName(actionInfo.ReflectedType);

            this.m_binderFactories = new ReadOnlyCollection<Func<IRouteBinder>>(
                actionInfo.GetParameters().Select(p => GetBinderFactory(p)).ToList());

        }

        public string Name { get; private set; }

        public string Area { get; private set; }

        public string RouteName { get; private set; }

        public string ControllerName { get; private set; }

        public IRouteBinder GetBinder(int index)
        {
            return this.m_binderFactories[index].Invoke();
        }

        private static Func<IRouteBinder> GetBinderFactory(ParameterInfo parameterInfo)
        {
            var binderAttribute = GetBinderAttribute(parameterInfo) ?? GetBinderAttribute(parameterInfo.ParameterType);

            var binder = binderAttribute != null ? binderAttribute.GetBinder() as IRouteBinder :
                ModelBinders.Binders.GetBinder(parameterInfo.ParameterType) as IRouteBinder;

            if (binder == null) return () => null;

            if (IsReusable(binder.GetType())) return () => binder;

            if (binderAttribute != null && IsReusable(binderAttribute.GetType()))
            {
                return () => binderAttribute.GetBinder() as IRouteBinder;
            }

            return () =>
            {
                var innerAttr = GetBinderAttribute(parameterInfo) ?? GetBinderAttribute(parameterInfo.ParameterType);
                return innerAttr != null ? innerAttr.GetBinder() as IRouteBinder :
                    ModelBinders.Binders.GetBinder(parameterInfo.ParameterType) as IRouteBinder;
            };
        }

        private static bool IsReusable(ICustomAttributeProvider provider)
        {
            return provider.GetCustomAttributes(typeof(ReusableAttribute), true).Length > 0;
        }

        private static string GetArea(Type type)
        {
            string areaTypeName = type.FullName;

            string[] strList = areaTypeName.Split('.');
            string areaName = "";
            bool has = false;
            int i = 0, temp = 0;
            foreach (string x in strList)
            {
                if (x == "Areas")
                {
                    has = true;
                    temp = i + 1;
                }
                if (has && temp == i) areaName = x;
                i++;
            }

            if (has && areaName.Length == 0)
            {
                throw new ArgumentException("Cannot route to the Controller class", "action");
            }

            return areaName;
        }

        private static string GetControllerName(Type controllerType)
        {
            string controllerTypeName = controllerType.Name;
            if (!controllerTypeName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException("The controller name must end with 'Controller'.", "action");
            }

            var controllerName = controllerTypeName.Substring(0, controllerTypeName.Length - "Controller".Length);
            if (controllerName.Length == 0)
            {
                throw new ArgumentException("Cannot route to the Controller class", "action");
            }

            return controllerName;
        }

        private static string GetName(MethodInfo actionInfo)
        {
            var attribute = (ActionNameAttribute)actionInfo.GetCustomAttributes(typeof(ActionNameAttribute), false).SingleOrDefault();
            return attribute == null ? actionInfo.Name : attribute.Name;
        }

        private static string GetRouteName(ICustomAttributeProvider actionInfo)
        {
            var attribute = (RouteNameAttribute)actionInfo.GetCustomAttributes(typeof(RouteNameAttribute), false).SingleOrDefault();
            return attribute == null ? "" : attribute.Name;
        }

        private static CustomModelBinderAttribute GetBinderAttribute(ICustomAttributeProvider element)
        {
            var attrs = (CustomModelBinderAttribute[])element.GetCustomAttributes(typeof(CustomModelBinderAttribute), true /* inherit */);
            if (attrs == null) return null;

            switch (attrs.Length)
            {
                case 0: return null;
                case 1: return attrs[0];
                default:
                    throw new InvalidOperationException("Multiple binder is forbidden.");
            }
        }
    }
}
